package org.geoserver.jtilecache;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.geoserver.jtilecache.cache.CacheException;
import org.geoserver.jtilecache.cache.WMSCache;
import org.geoserver.jtilecache.mime.ImageMimeType;
import org.geoserver.jtilecache.service.Connection;
import org.geoserver.jtilecache.service.Request;
import org.geoserver.jtilecache.service.Response;
import org.geoserver.jtilecache.service.wms.WMSParameters;
import org.geoserver.jtilecache.tile.ImageTile;
import org.geoserver.jtilecache.tile.Layer;
import org.geoserver.jtilecache.util.Configuration;

public class JTileCache extends HttpServlet {

	/**
	 * @serial
	 */
	private static final long serialVersionUID = 4175613925719485006L;

	private static Log log = LogFactory.getLog(org.geoserver.jtilecache.JTileCache.class);

	private static WMSCache cache = null;

	public void init() {

		// Load the configuration
		Configuration.getInstance();

		// Setup the cache
		try {
			cache = WMSCache.getInstance();
		} catch (CacheException ce) {
			log.fatal("Could not setup cache!", ce);
		}

		try {
			cache.setUp();
		} catch (CacheException ce) {
			log.error("Could not setup cache: ", ce);
		}
	}

	public void doGet(HttpServletRequest request,
						HttpServletResponse response)
						throws ServletException, IOException {

		if(log.isDebugEnabled()) {
			log.debug("-------------New Request-----------");
			log.debug(request.getRequestURI());
		}

		long starttime = 0;
		if(log.isInfoEnabled()) {
			starttime = System.currentTimeMillis();
		}

		WMSParameters wmsparams = new WMSParameters(request);

		ImageTile img = null;

		// Try to fetch image from cache
		img = getFromCache(wmsparams);

		Layer layer = new Layer(wmsparams);

		if(img != null) {
		// Image was pulled from cache

			if(log.isTraceEnabled()) {
				log.trace("Fetched image from cache.");
			}

			sendImage(response, img);

		// TODO: this should be incorporated into better layer-based workflow
		} else if(layer.isInLayer(wmsparams)) {

			// Otherwise, ask the WMS server for the image

			if(log.isTraceEnabled()) {
				log.trace("Image not in cache, asking WMS server.");
			}

			// Create an outgoing WMS request to the server
			Request wmsrequest = new Request(wmsparams);

			Connection connection = new Connection(wmsrequest);

			try {
				connection.connect();
			} catch(IOException ioe) {
				log.error("Could not connect to WMS: ", ioe);
				throw ioe;
			}

			Response wmsresponse = connection.getResponse();

			// If we have a proper response, try to put it into the cache

			// Test if the result is an image
			if(ImageMimeType.isImageMime(wmsresponse.getMimetype().toString())) {
				try {

					img = new ImageTile(layer, wmsresponse.getInputStream());

					// Send the image
					sendImage(response, img);

					// Then set into cache
					setToCache(wmsparams, img);
				} catch (IOException ioe) {
					log.error("I/O Error", ioe);
					log.info("Outputting as binary stream instead of image.");
					sendBinary(response, wmsresponse);
				}
			} else {
				log.info("Outputting as binary stream instead of image.");
				sendBinary(response, wmsresponse);
			}
		}

		if(log.isDebugEnabled()) {
			log.debug("Request processed in " + (System.currentTimeMillis() - starttime) + " ms.");
		}
	}

	private ImageTile getFromCache(WMSParameters wmsparams) {
		ImageTile img = null;

		log.trace("Attempting to get image from cache.");

		try {
			img = cache.get(wmsparams);
		} catch (CacheException ce) {
			log.error("Error retrieving from cache: ", ce);
		} catch (NullPointerException npe) {
			log.error("Cache is not intialized, cannot set to cache.", npe);
		}

		return img;
	}

	private void setToCache(WMSParameters params, ImageTile img) {
		log.trace("Attempting to set image to cache.");
		try {
			cache.set(params, img);
		} catch (CacheException ce) {
			log.error("Error setting to cache: ", ce);
		} catch (NullPointerException npe) {
			log.error("Cache is not intialized, cannot set to cache.", npe);
		}
	}

	private void sendImage(HttpServletResponse response, ImageTile img) throws IOException {

		log.trace("Sending image.");

		ImageMimeType mime = img.getMime();

		response.setContentType(mime.getMime());
		response.setCharacterEncoding(mime.getEncoding());

		img.write(response.getOutputStream());

	}

	private void sendBinary(HttpServletResponse response, Response wmsresponse) throws IOException{

		if(log.isTraceEnabled()) {
			log.trace("Sending binary stream (proxy of server response).");
		}

		if(Configuration.getInstance().getForwardOnError() == true) {
			response.setContentType(wmsresponse.getMimetype().getMime());
			response.setCharacterEncoding(wmsresponse.getMimetype().getEncoding());

			OutputStream out = response.getOutputStream();
			InputStream in = wmsresponse.getInputStream();

			for(int c = in.read(); c != -1; c = in.read()) {
				out.write(c);
			}
			out.flush();
		}
	}
}